Projet EDA : NetFlix¶

Netflix collects all the data you can imagine and even more! They have millions of dollars of budget every year to invest into new content with the goal of pleasing the customer, increase the watch time, decrease the churn and eventually grow the subscriber base.

Today they need your help visualising the current state of their catalogue.

GOALS 🎯

  • Ingest the dataset
  • EDA it
  • Create some Visualisationa and metrics to :
    • Identify which genres are the most present on the platform
    • Identify in how many countries Netflix produced it content ?
    • Identify which country has the most content
    • Identify dominant genre per country
    • Identify if there are seasonality in the catalogue release
In [1]:
# importation des librairies/modules 

import pandas as pd
import re 
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import plotly.io as pio
import plotly.express as px
import plotly.graph_objects as go  
from plotly.subplots import make_subplots
from IPython.display import display, HTML


pd.set_option('display.max_columns', None)
In [2]:
# import des données
dataset = pd.read_csv("./CSV/netflix_titles.csv", encoding='iso-8859-1')

# -- On définit des variables pour une exploration rapide du dataset -- #
# cinq premières lignes du dataset
head_dataset = dataset.head(n=10)
# Statistiques de base du dataset
stats_basiques = dataset.describe()
# Trouver les valeurs manquantes
valeur_manquantes = dataset.isnull().sum()
# identifier le noms des colonnes
nom_des_colonnes = dataset.columns
# identifier le type des colonnes
shape_dataset = dataset.shape
In [3]:
head_dataset
Out[3]:
show_id type title director cast country date_added release_year rating duration listed_in description
0 s1 Movie Dick Johnson Is Dead Kirsten Johnson NaN United States September 25, 2021 2020 PG-13 90 min Documentaries As her father nears the end of his life, filmm...
1 s2 TV Show Blood & Water NaN Ama Qamata, Khosi Ngema, Gail Mabalane, Thaban... South Africa September 24, 2021 2021 TV-MA 2 Seasons International TV Shows, TV Dramas, TV Mysteries After crossing paths at a party, a Cape Town t...
2 s3 TV Show Ganglands Julien Leclercq Sami Bouajila, Tracy Gotoas, Samuel Jouy, Nabi... NaN September 24, 2021 2021 TV-MA 1 Season Crime TV Shows, International TV Shows, TV Act... To protect his family from a powerful drug lor...
3 s4 TV Show Jailbirds New Orleans NaN NaN NaN September 24, 2021 2021 TV-MA 1 Season Docuseries, Reality TV Feuds, flirtations and toilet talk go down amo...
4 s5 TV Show Kota Factory NaN Mayur More, Jitendra Kumar, Ranjan Raj, Alam K... India September 24, 2021 2021 TV-MA 2 Seasons International TV Shows, Romantic TV Shows, TV ... In a city of coaching centers known to train I...
5 s6 TV Show Midnight Mass Mike Flanagan Kate Siegel, Zach Gilford, Hamish Linklater, H... NaN September 24, 2021 2021 TV-MA 1 Season TV Dramas, TV Horror, TV Mysteries The arrival of a charismatic young priest brin...
6 s7 Movie My Little Pony: A New Generation Robert Cullen, José Luis Ucha Vanessa Hudgens, Kimiko Glenn, James Marsden, ... NaN September 24, 2021 2021 PG 91 min Children & Family Movies Equestria's divided. But a bright-eyed hero be...
7 s8 Movie Sankofa Haile Gerima Kofi Ghanaba, Oyafunmike Ogunlano, Alexandra D... United States, Ghana, Burkina Faso, United Kin... September 24, 2021 1993 TV-MA 125 min Dramas, Independent Movies, International Movies On a photo shoot in Ghana, an American model s...
8 s9 TV Show The Great British Baking Show Andy Devonshire Mel Giedroyc, Sue Perkins, Mary Berry, Paul Ho... United Kingdom September 24, 2021 2021 TV-14 9 Seasons British TV Shows, Reality TV A talented batch of amateur bakers face off in...
9 s10 Movie The Starling Theodore Melfi Melissa McCarthy, Chris O'Dowd, Kevin Kline, T... United States September 24, 2021 2021 PG-13 104 min Comedies, Dramas A woman adjusting to life after a loss contend...
In [4]:
dataset['country'].isnull().sum()
Out[4]:
831

Changement de la colonne date_added¶

Cette colonne contient les dates en format chaine de caractères, pour une meilleure manipulation des données on passe au type datetime.

In [5]:
dataset['date_added'] = pd.to_datetime(dataset['date_added'])

Ajout d'une colonne "Is_international"¶

Nous allons crée une colonne pour determiner si un film à la caratéristique international.

Un film international est un film qui n'a pas été produit en majorit au Etats-Unis.

In [6]:
""" 
On va rajouter une colonne qui va nous permettre de savoir si le film est internationnal ou non
Dans la colonne listed_in il y a les catagories de films 'International Movies' et 'International TV Shows'
"""

# On crée une fonction qui va nous permettre de savoir si le film est international ou non
def is_international(liste_genre):
    try:
        liste_genre = [genre.strip() for genre in liste_genre.split(',')]
        for genre in liste_genre:
            if re.match('^International Movies$', genre) or re.match('^International TV Shows$', genre):
                return 1
            else:
                return 0
    except:
        pass


# On applique la fonction à la colonne listed_in
dataset['is_international'] = dataset['listed_in'].apply(is_international)
dataset['is_international'].value_counts()
# On voit qu'il y a 2430 films internationaux et 5377 films non internationaux
Out[6]:
0    7905
1     902
Name: is_international, dtype: int64
In [7]:
""" 
La colonne listed_in contient plusieurs genres séprarés par des virgules.
Certains genre sont Movies et TV Show.
Cela donne un contenu de "type" Movie et de genre "Movie" ce qui n'est pas utile et pourrait fausser les résultats.
Une solution serait de supprimer les genres Movie et Tv Show de la colonne listed_in.
Avec la fonction apply, on peut appliquer une fonction à chaque ligne du dataset.
Cette fonction va retirer les genres Movie et Tv Show de la colonne listed_in.

En REGEX, Il est important de chercher les mots Movie/Tv Show entre des caractères spéciaux pour les cibler spécifiquement.

"""

def replace_genre(liste_genre):
    # je split la liste des genres par des virgules
    genres_a_supprimer = ['^TV Shows$', '^Movies$', "^International Movies$", "^International TV Shows$"]
    try:
        liste_genre = [genre.strip() for genre in liste_genre.split(',')]
        for genre in liste_genre:
            for genre_a_supprimer in genres_a_supprimer:
                if re.match(genre_a_supprimer, genre):
                    liste_genre.remove(genre)
        # je reconstitue la liste des genres en les séparant par des virgules
        liste_genre = ', '.join(liste_genre)
        return liste_genre
    except:
        pass


dataset['listed_in'] = dataset['listed_in'].apply(replace_genre)


    

Identifier les genres dominant pour les films et les séries TV¶

In [8]:
dataset_movies = dataset[dataset['type'] == 'Movie']

dataset_tv_shows = dataset[dataset['type'] == 'TV Show']
In [9]:
top_genre_movies = dataset_movies['listed_in'].str.split(',').explode().str.strip().value_counts()
top_genre_tv_shows = dataset_tv_shows['listed_in'].str.split(',').explode().str.strip().value_counts()

html_output = f"<div style='display: flex;'><div style='margin-right: 20px;'>{top_genre_movies.to_frame().to_html()}</div><div>{top_genre_tv_shows.to_frame().to_html()}</div></div>"
display(HTML(html_output))
listed_in
Dramas 2427
Comedies 1674
Documentaries 869
Action & Adventure 859
Independent Movies 756
Children & Family Movies 641
Romantic Movies 616
Thrillers 577
Music & Musicals 375
Horror Movies 357
Stand-Up Comedy 343
Sci-Fi & Fantasy 243
Sports Movies 219
Classic Movies 116
LGBTQ Movies 102
Anime Features 71
Cult Movies 71
Faith & Spirituality 65
60
listed_in
TV Dramas 763
TV Comedies 581
Crime TV Shows 470
Kids' TV 451
Docuseries 395
Romantic TV Shows 370
Reality TV 255
British TV Shows 253
Anime Series 176
Spanish-Language TV Shows 174
TV Action & Adventure 168
Korean TV Shows 151
TV Mysteries 98
Science & Nature TV 92
TV Sci-Fi & Fantasy 84
TV Horror 75
Teen TV Shows 69
TV Thrillers 57
Stand-Up Comedy & Talk Shows 56
Classic & Cult TV 28
18
In [10]:
fig = make_subplots(rows=2, 
                    cols=1, 
                    subplot_titles=['Nombre de films par genre', 'Nombre de Tv Shows par genre'],
                    shared_xaxes=False
                    )
fig.add_trace(go.Bar(x=top_genre_movies.index, y=top_genre_movies.values, name='Movie'), row=1, col=1)

fig.add_trace(go.Bar(x=top_genre_tv_shows.index, y=top_genre_tv_shows.values, name='Tv Show'), row=2, col=1)

fig.update_layout(height=750, width=800)
fig.show()

Répartition des pays producteurs dans le monde¶

In [11]:
pays_producteurs = dataset['country'].str.split(',').explode().str.strip().unique()

pays_producteurs = pd.DataFrame(pays_producteurs, columns=['Pays'])

fig = px.choropleth(pays_producteurs, locations="Pays", color_discrete_sequence=["red"], locationmode="country names")
fig.update_layout(title_text='Pays producteur de contenu sur Netflix', title_x=0.5)
fig.show()

Le volume de production par pays¶

In [12]:
volume_production_pays = dataset['country'].str.split(',').explode().str.strip().value_counts()

volume_production_pays
Out[12]:
United States     3690
India             1046
United Kingdom     806
Canada             445
France             393
                  ... 
Ecuador              1
Armenia              1
Mongolia             1
Bahamas              1
Montenegro           1
Name: country, Length: 123, dtype: int64
In [13]:
fig = go.Figure()
fig.add_trace(go.Bar(x=volume_production_pays.index, 
                     y=volume_production_pays.values, 
                     text=volume_production_pays.values, 
                     texttemplate='%{text:.2s}', 
                     textposition='outside')
                     )
fig.update_layout(title_text='Volume de production par pays', title_x=0.5)
fig.show()


data = {'Pays': volume_production_pays.index, 'Volume de production': volume_production_pays.values}
df = pd.DataFrame(data)

fig = px.choropleth(df, locations="Pays", color="Volume de production", color_continuous_scale=px.colors.sequential.OrRd,  range_color=[1, 500], locationmode="country names")
fig.update_layout(title_text='Production de contenu par pays', title_x=0.5)

fig.show()

Identifier les genres dominant par pays¶

In [14]:
# la liste des pays producteurs
liste_country = dataset['country'].dropna().str.split(',').explode().str.strip().unique()

def get_top_genre_movies(liste_country):
    liste_resultat = []
    for country in liste_country:
        dataset_country = dataset[dataset['country'].str.contains(country, na=False)]
        dataset_country_movies = dataset_country[dataset_country['type'] == 'Movie']
        top_genre_movies = dataset_country_movies['listed_in'].dropna().str.split(',').explode().str.strip().value_counts()
        for i in range(len(top_genre_movies)):
            liste_resultat.append({"Pays": country, "Genre": top_genre_movies.index[i], "Valeurs": top_genre_movies.values[i]})
    return liste_resultat

def get_top_genre_tvshow(liste_country):
    liste_resultat = []
    for country in liste_country:
        dataset_country = dataset[dataset['country'].str.contains(country, na=False)]
        dataset_country_tv_show = dataset_country[dataset_country['type'] == 'TV Show']
        top_genre_tv_shows = dataset_country_tv_show['listed_in'].dropna().str.split(',').explode().str.strip().value_counts()
        for i in range(len(top_genre_tv_shows)):
            liste_resultat.append({"Pays": country, "Genre": top_genre_tv_shows.index[i], "Valeurs": top_genre_tv_shows.values[i]})
    return liste_resultat

top_genre_country_movie = pd.DataFrame(get_top_genre_movies(liste_country))
top_genre_country_tvshow = pd.DataFrame(get_top_genre_tvshow(liste_country))

# Les résultats contient la somme par genre ce qui fausse le treemap et affiche des genres sans pays associés
# on supprime les lignes en question
for i in range(len(top_genre_country_movie)):
    if top_genre_country_movie['Pays'][i] == '':
        top_genre_country_movie.drop(i, inplace=True)

for i in range(len(top_genre_country_tvshow)):
    if top_genre_country_tvshow['Pays'][i] == '':
        top_genre_country_tvshow.drop(i, inplace=True)
In [15]:
labels_m = [genre for genre in top_genre_country_movie['Genre']]
parents_m = ['' for i in range(len(top_genre_country_movie))]
values_m = [valeur for valeur in top_genre_country_movie['Valeurs']]
pays_m = [p for p in top_genre_country_movie['Pays']]

# je créer un DataFrame avec les listes pays, labels, values et parents
data_m = {'Pays': pays_m, 'labels': labels_m, 'values': values_m, 'parents': parents_m}
df_m = pd.DataFrame(data_m)

fig = px.treemap(df_m, path=['Pays', 'labels'], values='values', color='values', color_continuous_scale='RdBu')
fig.update_layout(title_text="Top Movie genre par pays", title_x=0.5)
fig.show()


labels_m = [genre for genre in top_genre_country_tvshow['Genre']]
parents_m = ['' for i in range(len(top_genre_country_tvshow))]
values_m = [valeur for valeur in top_genre_country_tvshow['Valeurs']]
pays_m = [p for p in top_genre_country_tvshow['Pays']]

# je créer un DataFrame avec les listes pays, labels, values et parents
data_m = {'Pays': pays_m, 'labels': labels_m, 'values': values_m, 'parents': parents_m}
df_m = pd.DataFrame(data_m)

fig = px.treemap(df_m, path=['Pays', 'labels'], values='values', color='values', color_continuous_scale='RdBu')
fig.update_layout(title_text="Top TV Show genre par pays", title_x=0.5)
fig.show()
/usr/lib/python3/dist-packages/plotly/express/_core.py:1637: FutureWarning:

The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.

/usr/lib/python3/dist-packages/plotly/express/_core.py:1637: FutureWarning:

The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.

/usr/lib/python3/dist-packages/plotly/express/_core.py:1637: FutureWarning:

The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.

/usr/lib/python3/dist-packages/plotly/express/_core.py:1637: FutureWarning:

The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.

Identifier s'il y a une saisonalité dans les sorties de contenues sur la plateforme¶

In [16]:
# pour la saisonalité, faire un graphique sur le temps on voit des piques à chaque début de mois

# la date est en datetime de la forme yyyy-mm-dd

#je selectionne les années de 2017 à 2021	
liste_years = [2016, 2017, 2018,2019,2020,2021]
liste_month = ["January","February","March","April","May","June","July","August","September","October","November","December"]
# je créer une liste vide qui va contenir les résultats de la boucle
liste_release_per_month = []

# pour chaque année je compte le nombre d'occurence par mois
# pour cela j'utilise une boucle for qui va parcourir la liste des années
# je selectionne les années avec .dt.year ce qui permet de comparer les années dans e dataset avec celle de la liste
# pour chaque année je compte le nombre d'occurence par mois avec .dt.month.value_counts().sort_index()
# cela me permettra de créer un graphique avec plotly ou j'affiche les sorties par mois pour chaque année
for year in liste_years:
    dataset_year = dataset[dataset['date_added'].dt.year == year]
    result = dataset_year['date_added'].dt.month.value_counts().sort_index()
    liste_release_per_month.append({"Year": year, "Release by Month": result})

  
# je crée un subplots dans lequel je vais afficher les graphiques de 2017 à 2021
fig = make_subplots(rows=3, 
                    cols=2, 
                    shared_xaxes=False 
                    )



# je créer un graphique qui présente les genres les plus populaires par pays en utilisant la liste liste_top_genre_per_country

# je crée un boucle qui va crée des graphiques avec la fonction add_trace dans le subplots.
# la difficiulté =» je dois incrémenter le row et le col pour placer le graphique dans la bonne case pour ne pas les avoirs tous à la suite
# je rajoute une condition qui bascule sur le second fig.add_trace qui incrémente sur la colonne 2 et sur les ligne 1 et 2 de la seconde colonne.
# pour cela le numéro de la row correspond à la valeur i moins trois 
# par exemple arrivé à l'itéaration 4 cela donne i(=4) - 3 pur être sur la première ligne de la seconde colonne
for i in range(1, len(liste_release_per_month)+1):
    if i <= 3:
        fig.add_trace(go.Bar(
                x=liste_month,
                y=liste_release_per_month[i-1]['Release by Month'].values, 
                text=liste_release_per_month[i-1]['Release by Month'].values, 
                texttemplate='%{text:.2s}', 
                textposition='outside', 
                name=liste_release_per_month[i-1]['Year']), 
                row=i, 
                col=1)
    else:
        fig.add_trace(go.Bar(
                x=liste_month,
                y=liste_release_per_month[i-1]['Release by Month'].values, 
                text=liste_release_per_month[i-1]['Release by Month'].values, 
                texttemplate='%{text:.2s}', 
                textposition='outside', 
                name=liste_release_per_month[i-1]['Year']), 
                row=i-3, 
                col=2)



# Personnaliser la mise en page
fig.update_layout(height=1000, width=900)

# je rajoute une légende  centré par rapport à la carte
fig.update_layout(title_text='Sorties Netflix par mois (2016-2021)', title_x=0.5)

fig.show()
In [17]:
calendirer_sortie = dataset.groupby('date_added')['show_id'].count()

fig = go.Figure(
    data = go.Scatter(
        x = calendirer_sortie.index, 
        y = calendirer_sortie.values,
       ),
    layout = go.Layout(
        title = go.layout.Title(text = "Sorties Netflix (2008-2021)", x = 0.5),
        xaxis = go.layout.XAxis(title = 'Date à laquelle les sorties ont été effectuées', rangeslider = go.layout.xaxis.Rangeslider(visible = True)),
        yaxis = go.layout.YAxis(title = 'Volumz de sortie')
    )
)

fig.show(renderer="notebook_connected")
In [ ]:
 
In [ ]: